<script setup>
import {onBeforeMount, onMounted, ref} from 'vue'
import * as THREE from 'three'
import map from './constant/SMap.json'
import {SCScene} from './constant/sc.js'
// 引入轨道控制器扩展库OrbitControls.js
import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
import {handleData} from "../utlis/index.js";
import {Easing, Tween} from '@tweenjs/tween.js'
import borderMap from "./constant/boundary.json";
import flyLine from "./constant/flyLine.json";
import * as d3 from "d3";
import {gsap} from 'gsap';
import {BufferAttribute, BufferGeometry} from "three";

const d3Function = d3.geoMercator().center([109, 34.5]).scale(1000).translate([0, 0])

// 拿到页面的宽高
const width = window.innerWidth, height = window.innerHeight;

// 创建场景
const scene = new THREE.Scene();
let showScene = scene

// 将背景颜色设置为白色
scene.background = new THREE.Color("#000000");

// 创建相机
const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10000);
// 设置相机位置
camera.position.z = 1000;
camera.position.x = 0
camera.position.y = -500

// 辅助线 AxesHelper
const axesHelper = new THREE.AxesHelper(500);
scene.add(axesHelper);

// 初始化渲染器
const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(width, height);

// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
//阻尼 更真实
controls.enableDamping = true

// 地图数据处理
const initGeom = () => {
  // 将地图数据显示在屏幕上
  // 如果是服务器返回的数据，需要先将数据转为geojson格式 这里默认用的本地数据
  handleData(map, scene, [109, 34.5], 1000, 0)
}
const ScScene = SCScene()

// 创建动画进场3d 视角转化特效
const tween = new Tween({x: 2000, y: 2000, z: 3000}) // 起始位置
    .to(camera.position, 5000) // 最终位置
    .easing(Easing.Quadratic.Out) // 过度动画
    .onUpdate((object) => { // 改变摄像机位置
      camera.position.x = object.x
      camera.position.y = object.y
      camera.position.z = object.z
    })
    .start();


let currentPos = 0
let positions;
let opacity;
let linePoints = []
let opacityGeometry;
let opacityPoints;
// 渲染页面
const render = () => {
  if (activeProvince === '四川省') {
    renderer.render(ScScene, camera)
    let info = document.querySelector('#info')
    info.style.display = 'none'
    showScene = ScScene
  } else {
    renderer.render(showScene, camera)
  }
  if (!activeProvince) {
    showScene = scene
    renderer.render(showScene, camera)
  }
  // 如果有地图边界所有坐标
  if (linePoints && opacity) {
    // 5234怎么来的：linePoints打印出来我这边有7134个  但是我这边有900个边界点我这边不想显示（也就是我视频下面那一坨很杂碎的地方）所以我截取了5234个
    //currentPos 也就是数组下标 只取前面4850个点
    if (currentPos > 4850) {
      // 移除边缘动画线
      // showScene.remove(opacityPoints)
      opacity[currentPos] = 0;
      currentPos = 0;
    }
    // 20  就是运动的速度  值越大 移动越快
    currentPos += 20;
    // 忘记这是干嘛的
    for (let i = 0; i < 1000; i++) {
      opacity[(currentPos - i) % linePoints.length] = 0;
    }
    // 100 代表粒子数量 越大表示动画的线越长
    for (let i = 0; i < 200; i++) {
      opacity[(currentPos + i) % linePoints.length] = i / 50 > 2 ? 2 : i / 50;
    }
    // 让点 动起来
    if (opacityGeometry) {
      opacityGeometry.attributes.aOpacity.needsUpdate = true;
    }
  }
  // 渲染下一帧的时候会调用render函数
  requestAnimationFrame(render)
  tween.update()
  controls.update()
}

const initBorderPoint = () => {
  // 获取地图边界的左边（通过https://datav.aliyun.com/portal/school/atlas/area_generator#2.51/104.299012/33.447963 获取）
  let borderPoints3Array = borderMap.features[0].geometry.coordinates
  let borderPoints = []
  borderPoints3Array.forEach((coordinate) => {
    coordinate.forEach((rows) => {
      rows.forEach(v => {
        // 将坐标转化为 d3的坐标
        const [x_XYZ, y_XYZ] = d3.geoMercator().center([109, 34.5]).scale(1000).translate([0, 0])(v)
        borderPoints.push({x: x_XYZ, y: -y_XYZ})
        // 将转换后的坐标放入到一个数组存储起来（重点！！！）
        linePoints.push([x_XYZ, -y_XYZ, 32])
      })
    });
  });
  opacityGeometry = new THREE.BufferGeometry();
  // 将数组转化为一维数组
  positions = new Float32Array(linePoints.flat(1));
  // 然后3个一组
  opacityGeometry.setAttribute("position", new BufferAttribute(positions, 3));
  opacity = new Float32Array(positions.length).map(() => 0);
  opacityGeometry.setAttribute("aOpacity", new BufferAttribute(opacity, 1));
  // 控制 颜色和粒子大小
  const params = {
    pointSize: 5.0,
    pointColor: '#37ffff'
  }
  // 看不懂写死
  const vertexShader = `
    attribute float aOpacity;
    uniform float uSize;
    varying float vOpacity;

    void main(){
        gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
        gl_PointSize = uSize;

        vOpacity=aOpacity;
    }
    `
  // 看不懂写死
  const fragmentShader = `
    varying float vOpacity;
    uniform vec3 uColor;

    float invert(float n){
        return 1.-n;
    }

    void main(){
      if(vOpacity <=0.2){
          discard;
      }
      vec2 uv=vec2(gl_PointCoord.x,invert(gl_PointCoord.y));
      vec2 cUv=2.*uv-1.;
      vec4 color=vec4(1./length(cUv));
      color*=vOpacity;
      color.rgb*=uColor;
      gl_FragColor=color;
    }
    `
  // 创建着色器
  const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    transparent: true, // 设置透明
    uniforms: {
      uSize: {
        value: params.pointSize
      },
      uColor: {
        value: new THREE.Color(params.pointColor)
      }
    }
  })
  //  创建一个点
  opacityPoints = new THREE.Points(opacityGeometry, material)
  // 将这个点放入到场景中
  showScene.add(opacityPoints);
}

const initLight = () => {
  // 基本光源
  const ambLight = new THREE.AmbientLight('#ffffff', 0.3)
  /**
   * 设置聚光灯相关的的属性
   */
  const spotLight = new THREE.SpotLight(0xFFFFFF); // 聚光灯
  spotLight.position.set(40, 200, 10);
  spotLight.castShadow = true; // 只有该属性为true时，该点光源允许产生阴影，并且下列属性可用
  scene.add(ambLight, spotLight); // 向场景中添加光源
}

const initFlyLine = () => {
  flyLine.forEach(item => {
    const [from_x, from_y] = d3Function(item.from)
    const [to_x, to_y] = d3Function(item.to)
    const start = new THREE.Vector3(from_x, -from_y, 32);  // 起点坐标

    const v1Point = [item.from[0] + (item.to[0] - item.from[0]) / 4, item.from[1] + (item.to[1] - item.from[1]) / 4]
    const [v1_x, v1_y] = d3Function(v1Point)
    const v1 = new THREE.Vector3(v1_x, -v1_y, 100);
    const v2Point = [item.from[0] + ((item.to[0] - item.from[0]) * 3) / 4, item.from[1] + ((item.to[1] - item.from[1]) * 3) / 4]
    const [v2_x, v2_y] = d3Function(v2Point)
    const v2 = new THREE.Vector3(v2_x, -v2_y, 100);

    const end = new THREE.Vector3(to_x, -to_y, 32);  // 终点坐标

    // 创建三次贝塞尔曲线
    const curve = new THREE.CubicBezierCurve3(start, v1, v2, end);

    // 使用 TubeGeometry 根据贝塞尔曲线绘制管道
    const tubeGeometry = new THREE.TubeGeometry(curve, 256, 1, 8, false); // 曲线256段, 半径2, 20个横截面

    // 创建材质，颜色为白色
    const material = new THREE.MeshBasicMaterial({color: item.color});

    // 创建管道网格
    const tubeMesh = new THREE.Mesh(tubeGeometry, material);

    // 将管道添加到场景中
    showScene.add(tubeMesh);

    const points = curve.getPoints(1000)
    const bufferGeometry = createBufferGeometry(points)
    const shaderMaterial = createShaderMaterial()
    const point = new THREE.Points(bufferGeometry, shaderMaterial)
    showScene.add(point)
    // 设置着色器的动画
    gsap.fromTo(shaderMaterial.uniforms.uTime,
      { value: 0 },
      {
        // 实现飞线钻地效果需要让 动画节段数 = 飞线长度 + 飞线点数量
        value: 80 + 1000,
        duration: 3,
        repeat: -1,
        delay: 0,
        ease: "none",
        onUpdate: () => {},
      }
    );
  })
}

// 创建着色器
function createShaderMaterial() {
  // 起点颜色
  let color1 = "#0c57e3";
  return new THREE.ShaderMaterial({
    depthTest: false,
    uniforms: {
      // 线条颜色
      uColor: {
        value: new THREE.Color(color1),
      },
      // 时间1-1000
      uTime: {
        value: 0,
      },
      // 水滴宽度
      uWidth: {
        value: 200,
      },
      // 水滴长度
      uLength: {
        value: 80,
      },
      vSize: {
        value: 10.0
      },
    },
    vertexShader: /*glsl*/ `
        attribute float aIndex; // 内部属性 浮点 当前序号

        uniform float uTime; // 全局变量 浮点 当前时间

        uniform float uWidth; // 全局变量 浮点 线段宽度

        uniform vec3 uColor; // 全局变量 颜色 设置的颜色

        varying float vSize; // 片元变量（需要传递到片面着色器） 浮点 尺寸

        uniform float uLength; // 全局变量 浮点 线段长度

        void main(){
            vec4 viewPosition = viewMatrix * modelMatrix * vec4(position,1);

            gl_Position = projectionMatrix * viewPosition; // 顶点矩阵变换 设置各个点的位置

            // 当前顶点的位置处于线段长度内 则设置水滴大小
            if(aIndex >= uTime - uLength && aIndex < uTime){
              // 水滴大小根据当前位置慢慢变小
              // p1 uWidth越大水滴越粗
              // vSize = uWidth * ((aIndex - uTime + uLength) / uLength);
              // p2 uWidth越大水滴越细
              vSize = (aIndex + uLength - uTime) / uWidth;
            }
            gl_PointSize = vSize;
        }
      `,
    fragmentShader: /*glsl*/ `
        varying float vSize;
        uniform vec3 uColor;
        void main(){
            // 透明度根据当前大小确定是否展示
            if(vSize<=0.0){
              gl_FragColor = vec4(1,0,0,0);
            }else{
              gl_FragColor = vec4(uColor,1);
            }
        }
      `,
    transparent: true,
    vertexColors: false,
  });
}

// 创建bufferGeometry
function createBufferGeometry(points) {
  const indexList = points.map((_, index) => index);
  const bufferGeometry = new BufferGeometry().setFromPoints(points);
  // 给几何体添加自定义的索引标识 用来后续根据索引设置点的透明度
  bufferGeometry.setAttribute(
      "aIndex",
      new THREE.Float32BufferAttribute(indexList, 1)
  );
  return bufferGeometry;
}

// 监听浏览器窗口的大小变化 重新调整渲染器的大小
window.addEventListener('resize', onWindowResize);

// 更新渲染器大小以匹配新的浏览器窗口尺寸
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

// 当鼠标划过省份的时候显示一些数据
// window.addEventListener('pointermove', onPointerMove);

// 光线投射Raycaster  在三维空间中计算出鼠标移过了什么物体
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
//鼠标放上去 改变颜色 显示地区名字
let activeIntersects = []; //鼠标滑过数据
let activeProvince = null
let lastIntersectedObject = null; // 保存上一次交互的子级 Object3D
const originalZPositions = new Map(); // 用于存储每个子级 Object3D 的原始 Z 轴位置
function onPointerMove(event) {
  let info = document.querySelector('#info')
  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
  // 通过摄像机和鼠标位置更新射线
  raycaster.setFromCamera(pointer, camera);

  // 判断数组是否有数据，有数据全部设置为原始数据
  if (activeIntersects.length) {
    for (let i = 0; i < activeIntersects.length; i++) {
      // 将滑动到的省份区域设置颜色
      activeIntersects[i].object.material.color.set('#d13a34');
    }
  }
  // 计算物体和射线的焦点
  const obj3d = showScene.children.filter(s => s.type === "Object3D")[0].children
  const intersects = raycaster.intersectObjects(obj3d, true);
  if (intersects.length) {
    // 设置hove 弹框的宽高
    info.style.left = event.clientX + 'px'
    info.style.top = event.clientY + 'px'
    info.style.display = 'block'
    info.innerHTML = intersects[0].object.parent.name
    const intersectedObject = intersects[0].object.parent; // 获取第一个相交的物体
    // 当碰撞到了CylinderGeometry和TextGeometry就不变颜色 （但是会抬高）
    const black = ["CylinderGeometry", 'TextGeometry']
    const intersectsType = intersects.map(s => s.object.geometry.type)
    if (intersectsType.some(v => black.includes((v)))) {
      return
    }
    if (showScene === scene) {
      // 如果上一次的 Object3D 不为空，且不是当前检测到的，恢复之前 Object3D 的 Z 轴
      if (lastIntersectedObject && lastIntersectedObject !== intersectedObject) {
        lastIntersectedObject.position.z = originalZPositions.get(lastIntersectedObject);
      }

      // 抬高当前 Object3D 的 Z 轴到 20
      intersectedObject.position.z = 20;

      // 更新最后交互的 Object3D
      lastIntersectedObject = intersectedObject;
    }
  } else {
    info.style.display = 'none'
    if (showScene === scene) {
      // 如果没有检测到任何物体，恢复所有子级 Object3D 的 Z 轴位置
      showScene.traverse(object => {
        if (object instanceof THREE.Object3D && originalZPositions.has(object)) {
          object.position.z = originalZPositions.get(object);
        }
      });

      // 清除上次交互的物体
      lastIntersectedObject = null;
    }
  }

  // 数组数据清空
  activeIntersects = []

  // 滑过的当前这个高亮
  for (let i = 0; i < intersects.length; i++) {
    if (intersects[i].object.type === 'Mesh') {
      intersects[i].object.material.color.set(0x52a8ff);
      activeIntersects.push(intersects[i])
    }
  }
}

function onPointerClick(event) {
  // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
  // 通过摄像机和鼠标位置更新射线
  raycaster.setFromCamera(pointer, camera);
  // 计算物体和射线的焦点
  const intersects = raycaster.intersectObjects(showScene.children);
  if (intersects.length && intersects[0].object.parent.name) {
    // 将点击的省份区域 存入到一个变量中 对后续的下钻操作准备
    activeProvince = intersects[0].object.parent.name
  }
}

// 地图的点击事件 然后进行地图的下钻操作
window.addEventListener('click', onPointerClick);
window.addEventListener('pointermove', onPointerMove);

const manager = THREE.DefaultLoadingManager;
manager.onStart = (url, itemsLoaded, itemsTotal)=> {
};

manager.onLoad = function () {
  console.log('All items loaded');
  // 这里隐藏 loading 效果，显示场景
  document.getElementById('loading').style.display = 'none';

  // 遍历 Object3D 的子物体，存储每个物体的初始 Z 轴位置
  const obj3d = showScene.children.filter(s => s.type === "Object3D")[0].children
  obj3d.forEach(object => {
    if (object.type === 'Object3D') {
      originalZPositions.set(object, object.position.z);
    }
  });
  render()
};

manager.onError = function (url) {
};

onBeforeMount(() => {
  // 添加物体到场景
  initGeom()
  // 创建边缘特效
  initBorderPoint()
  // 创建飞线
  initFlyLine()
  // 设置环境光
  initLight()
  // 渲染
  // render()
  // 将渲染加入到页面上
  document.body.appendChild(renderer.domElement);
})
</script>

<template>
  <div style="position: relative">
    <div id="info">
    </div>
    <button style="position: absolute;top: 0;left: 0;" @click="activeProvince = null">返回上一级</button>
  </div>
  <div class="loading-container" id="loading">
    <div class="spinner"></div>
  </div>
</template>

<style scoped>
#info {
  position: absolute;
  background: rgba(0, 0, 0, 0.5);
  color: #fff;
  border-radius: 2px;
  padding: 5px 10px;
  display: none;
  width: auto; /* 设置宽度自适应 */
}
/* 父容器 */
.loading-container {
  position: relative; /* 为了相对定位子元素 */
  width: 100%;
  height: 100vh; /* 或者你想要的高度 */
  display: flex;
  justify-content: center;
  align-items: center; /* 水平和垂直居中 */
  background-color: #f0f0f0;
}

/* 加载动画 */
.spinner {
  width: 50px;
  height: 50px;
  border: 5px solid rgba(0, 0, 0, 0.1);
  border-top: 5px solid #3498db;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

/* 动画效果 */
@keyframes spin {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
</style>